<?php

namespace Boosen\Company\app\Console\Commands;

use Illuminate\Console\Command;

use App\Models\Region;
use Log;

use Boosen\Company\app\Models\CNCompanyUrl;
use Boosen\Company\app\Models\Company;
use Boosen\Company\app\Models\CompanyRegisterInfo;
use Boosen\Company\app\Models\Keyword;
use Boosen\Company\app\Models\CompanyKeywordRel;
use Boosen\Company\app\Models\Industry;
use Boosen\Company\app\Models\CompanyIndustryRel;
use Boosen\Company\app\Models\CompanyDetail;

use Goutte\Client;
use GuzzleHttp\Client as GuzzleClient;

class CNCompanyInfoSpider extends Command
{
    const PAGE_SIZE = 50;
    const INFO_URL_SUFIX = 'company-information.html';

    const MAIN_PRODUCT = '主营产品或服务';
    const MAIN_INDUSTRY = '主营行业';

    private static $register_fields_tyc = array( // tian yan cha 
        'address' => '公司地址',
        'register_nb' => '注册号',
        'register_institute' => '注册机构',
        'register_asset' => '注册资本',
        'type' => '公司类型',
        'establish_date' => '成立日期',
        'legal_owner' => '法人代表',
        'business_scope' => '经营范围',
        'business_expire' => '营业期限',
    );

    private static $register_fields_normal = array( // tian yan cha 
        'address' => '公司注册地址',
        'register_nb' => '公司注册号',
        'register_institute' => '登记机关',
        'register_asset' => '注册资本',
        'type' => '企业类型',
        'establish_date' => '公司成立日期',
        'legal_owner' => '法定代表人/负责人',
        'business_scope' => '经营范围',
        'business_expire' => '营业期限',
    );

    private static $company_detail_fields = array(
        'model' => '经营模式',
        'employee_nb' => '员工人数',
        'is_oem' => '是否提供OEM代加工',
        'brand_name' => '品牌名称',
        'market' => '主要市场',
        'customer' => '主要客户群体',
        'business_location' => '主要经营地点',
        'rd_employee_nb' => '研发部门人数',
        // 'monthly_outpuy' => '月产量',
        'annual_sales' => '年营业额',
        'managment_certification' => '管理体系认证',
        'qc_certification' => '产品质量认证',
        'qc_control' => '质量控制',
        'plant_area' => '厂房面积',
        'main_equipment' => '主要设备',
        'annual_export' => '年出口额',
        'annual_import' => '年进口额'

    );
    
    private $cn_url_id;

    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'cn:company_info {start_id?}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    private $company;


    private $currentId;

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        // $url = 'https://linxglobal.cn.china.cn';
        // $url = 'https://dgxiao.cn.china.cn';

        // print_r($this->proxies);exit;
        $startId = $this->argument('start_id') ?: 1;
        $page = 1;

        $count = 0;

        // $this->proxy = $this->proxies[array_rand($this->proxies)];
        while (true) {
            $pages = CNCompanyUrl::where('id', '>=', $startId)->simplePaginate(self::PAGE_SIZE, ['*'], 'page', $page);

            $break = (bool)!(count($pages->items()) > 0);
            foreach ($pages->items() as $item) {
                $this->info($item->url. ' start......');
                $this->cn_url_id = $item->id;
                $break = !$this->processCompanyUrl($item);

                // sleep(5);
                // if ($count > 200) $break = true;
                if ($break) break;
                
                $count++;
                $this->info('---- ' . $item->id . 'th done-----');
                
                if ($item->id % 2) {
                    sleep(2); 
                }
            }
            if ($break) break;
            $page++;

            sleep(5); // 50 item sleep 5s
        }
    }

    private function processCompanyUrl(CNCompanyUrl $item)
    {
        // $url = 'https://hnspjx.cn.china.cn';
        $gutte = GoutteClient::getInstance();
        while (true) {
            try {
                $client = $gutte->client();
                $crawler = $client->request('GET', $item->url . '/' . self::INFO_URL_SUFIX);
                
                // check page type 
                $left = $crawler->filterXPath('//div[@class="con-lef"]');

                if ($left->count()) {
                    $this->getCompanyInfoWithLeft($crawler);
                    $this->getCompanyRegisterInfo($crawler, 1);
                    $this->getCompanyDetail($crawler);
                } else {
                    $this->getCompanyInfo($crawler);
                    $this->getCompanyRegisterInfo($crawler);
                    $this->getCompanyDetail($crawler);
                }

                break;
            } catch (\Exception $e) {
                $this->info($e->getMessage());

                if ($crawler->filterXPath('//div[@class="warn_cont"]')->count()) {
                    $gutte->changeProxy();
                    $this->info('proxy change to ' . $gutte->getProxy());
                } else {
                    // insert error message to DB

                    return false;
                }
                
                if (false !== strposa($e->getMessage(), array('time out', 'timed out', 'reset', 'aborted', 'refused'))) {
                    if ($gutte->hasProxy()) {
                        $gutte->unsetProxy();
                    }

                    if ($gutte->proxyCount() == 0) {
                        $this->info('no useful proxy ');
                        sleep(10);
                    }
                }

            } 
        }

        return true;
        
    }

    private function getCompanyInfoWithLeft($crawler) {
        $company = [];
        $labelItems = $crawler->filterXPath('//ul[@class="dot2"]/li/dl/dt');
        $valueItems = $crawler->filterXPath('//ul[@class="dot2"]/li/dl/dd');
        
        $labelItems->each(function($item, $i) use (&$company, $valueItems) {
            $value = $valueItems->eq($i);
            if (false !== mb_strpos($item->text(), '联系人')) {
                $company['contact_name'] = trim($value->text());
            }

            if (false !== mb_strpos($item->text(), '手 机')) {
                $company['phone'] = trim($value->text());
            }

            if (false !== mb_strpos($item->text(), '地 址')) {
                $location = trim($value->text());
                $company = array_merge($company, $this->getAddressArr($location));
            }
        });

        $qqItem = $crawler->filterXPath('//div[@class="online-cont fl-right"]/a');

        if ($qqItem->count() > 1) {
            $qqNode = $qqItem->getNode(0);
            if ($qqNode->hasAttribute('onclick')) {
                $onclick = $qqNode->getAttribute('onclick');
                if (preg_match('/\(\'(\d+)\'/', $onclick, $matches)) {
                    $company['qq'] = $matches[1];
                }
            }
        }

        $titleItem = $crawler->filterXPath('//h1[@class="pdmd"]/a');
        if ($titleItem->count() > 0) {
            $company['name'] = trim($titleItem->text());
        } else {
            throw new \Exception("Can not get company name", 100);
        }

        $logoItem = $crawler->filterXPath('//span[@class="logoPic"]/img');
        if ($logoItem->count() > 0) {
            $company['logo_pic'] = $logoItem->image()->getUri();
        }


        $companyPic = $crawler->filterXPath('//div[@class="company-pic"]/span/img');
        if ($companyPic->count() > 0) {
            $company['des_pic'] = $companyPic->image()->getUri();
        } else {
            // throw new \Exception("No description picture", 100);
        }

        $companyInfo = $crawler->filterXPath('//div[@class="company-info"]/pre');
        if ($companyInfo->count() > 0) {
            $company['description'] = trim($companyInfo->text());
        } else {
            // throw new \Exception("No description", 100);
        }

        // $company['cn_url_id'] = $this->cn_url_id;
        $this->company = Company::updateOrCreate(['cn_url_id' => $this->cn_url_id], $company);
        $this->info('Insert one company ' . $this->company->name);
        return $this->company;
    }

    private function getAddressArr($location)
    {
        $locationArr = explode(' ', $location);

        if (count($locationArr) == 3) {
            $areaWordPos = mb_strpos($locationArr[2], '镇');
            if ($areaWordPos === false) {
                $cityWordPos = mb_strpos($locationArr[2], '市');
                if ($cityWordPos === false) {
                    $city = Region::ofType(2)->where('name', 'like', $locationArr[1] . '%')->first();
                } else {
                    $locationArr[1] = mb_substr($locationArr[2], $cityWordPos - 2, 3);
                    $city = Region::ofType(2)->where('name', $locationArr[1])->first();
                }
            } else {
                $locationArr[3] = $locationArr[2];
                $locationArr[2] = mb_substr($locationArr[2], $areaWordPos - 2, 3);
            }
        }


        if (isset($city)) {
            $area = null; //$city->getFirstChild();
        } else {
            $area = Region::ofType(3)->where('name', $locationArr[2])->first();
            if (!$area) {
                // throw new \Exception("Can not find area " . $locationArr[2], 100);
                $city = Region::ofType(2)->where('name', 'like', $locationArr[1] . '%')->first();
                // $area = $city->getFirstChild();
            } else {
                $city = $area->getParent();
            }
            
        }
        
        $address['area_id'] = $area ? $area->id : null;
        $address['city_id'] = $city->id;
        $address['province_id'] = $city->parent_id;


        array_shift($locationArr);
        array_shift($locationArr);
        if (count($locationArr) > 3) {
            array_shift($locationArr);
        }

        $address['district'] = implode(' ', $locationArr);
        return $address;
    }

    private function getCompanyInfo($crawler)
    {
        $company = array();
        $infoCon = $crawler->filterXPath('//div[@class="ft_dz"]/span');
        if ($infoCon->count() == 0) {
            throw new \Exception("no company info", 100);
        }

        $locationItems = $infoCon->each(function($node, $i) use (&$company) {
            if ($i == 0) {
                $nameNode = $node->filter('a');
                if ($nameNode->count() > 0) {
                    $company['name'] = trim($nameNode->text());
                    // print_r();exit;
                } else {
                    throw new \Exception("Can not get company name", 100);
                }
            } else {
                if (false !== mb_strpos($node->text(), '手机')) {
                    $company['phone'] = trim(str_replace('手机：', '', $node->text()));
                }

                if (false !== mb_strpos($node->text(), '电话')) {
                    $company['tel_phone'] = trim(str_replace('电话：', '', $node->text()));
                }
                if (false !== mb_strpos($node->text(), '地址')) {
                    $location = trim(str_replace('地址：', '', $node->text()));
                    $company = array_merge($company, $this->getAddressArr($location));
                }
            }
        });

        $qqItem = $crawler->filterXPath('//a[@class="qjt"]');
        if ($qqItem->count() > 0) {
            $qqNode = $qqItem->getNode(0);

            if ($qqNode->hasAttribute('onclick')) {
                $onclick = $qqNode->getAttribute('onclick');
                if (preg_match('/\(\'(\d+)\'/', $onclick, $matches)) {
                    $company['qq'] = $matches[1];
                }
            }
        } else {
            // throw new \Exception("No qq found", 100);
        }

        // contact name 
        

        $contactItem = $crawler->filterXPath('//span[@class="linker"]');
        if ($contactItem->count() > 0) {
            $company['contact_name'] = trim($contactItem->text());
        }

        $companyLogo = $crawler->filterXPath('//div[@class="logo fl-clr"]/a');
        if ($companyLogo->count() > 0) {
            $company['logo_pic'] = $companyLogo->filter('img')->image()->getUri();
        } else {
            // throw new \Exception("No logo", 100);
        }
        
        $companyPic = $crawler->filterXPath('//div[@class="company-pic"]/span/img');
        if ($companyPic->count() > 0) {
            $company['des_pic'] = $companyPic->image()->getUri();
        } else {
            // throw new \Exception("No description picture", 100);
        }

        $companyInfo = $crawler->filterXPath('//div[@class="company-info"]/pre');
        if ($companyInfo->count() > 0) {
            $company['description'] = trim($companyInfo->text());
        } else {
            // throw new \Exception("No description", 100);
        }

        // $company['cn_url_id'] = $this->cn_url_id;
        $this->company = Company::updateOrCreate(['cn_url_id' => $this->cn_url_id], $company);
        $this->info('Insert one company ' . $this->company->name);
        return $this->company;
    }


    private function getCompanyRegisterInfo($crawler, $type=0)
    {
        $rInfo = array();
        $tycClass = 'check_eye';
        $registInfoClass = 'company-mod';
        if ($type == 1) {
            $tycClass = 'check_eye_0';
            $registInfoClass = 'company-mod fl-clr';
        }
        $tycItem = $crawler->filterXPath('//span[@class="' . $tycClass . '"]');
        $registerNames = $crawler->filterXPath('//div[@class="' . $registInfoClass . '"]/table/tbody/tr/th');
        $registerValues = $crawler->filterXPath('//div[@class="' . $registInfoClass . '"]/table/tbody/tr/td');
        if ($registerValues->count() != $registerNames->count()) {
            // throw new \Exception("Register info error", 100);
        }
        $fields = ($tycItem->count() > 0) ? self::$register_fields_tyc : self::$register_fields_normal;
        $rInfo['is_tyc'] = (int)$tycItem->count();
            

        $registerNames->each(function ($node, $i) use (&$rInfo, $registerValues, $fields) {
           $key = array_search(trim($node->text()), $fields);
           if (false !== $key) {
                $rInfo[$key] = trim($registerValues->eq($i)->text());
           } 
        });
        // $rInfo['company_id'] = $this->company->id;
        
        $info = CompanyRegisterInfo::updateOrCreate(
            ['company_id' => $this->company->id],
            $rInfo
        );
        $this->info('Register info for company: ' . $this->company->name);
        return $info;
    }

    private function getCompanyDetail($crawler)
    {
        $nameItems = $crawler->filterXPath('//div[@class="attr-mod"]/table/tbody/tr/th');
        $valueItems = $crawler->filterXPath('//div[@class="attr-mod"]/table/tbody/tr/td');

        if ($nameItems->count() != $valueItems->count()) {
            // throw new \Exception("Detail info error", 100);
        }
        $details = array();

        $nameItems->each(function($node, $i) use (&$details, $valueItems) {

            $key = array_search(trim($node->text()), self::$company_detail_fields);

            if (false !== $key) {
                $details[$key] = trim($valueItems->eq($i)->text());
            } else {
                if (trim($node->text()) == self::MAIN_PRODUCT) {
                    $products = $valueItems->eq($i)->filter('a');

                    $products->each(function($node, $i) {
                        $keyword = Keyword::firstOrCreate(['name' => trim($node->text())]);

                        CompanyKeywordRel::firstOrCreate([
                            'company_id' => $this->company->id, 
                            'keyword_id' => $keyword->id
                        ]);
                    });
                }


                if (trim($node->text()) == self::MAIN_INDUSTRY) {
                    $industries = $valueItems->eq($i)->filter('a');

                    $industries->each(function($node, $i) {
                        $industry = Industry::firstOrCreate(['name' => trim($node->text())]);

                        CompanyIndustryRel::firstOrCreate([
                            'company_id' => $this->company->id, 
                            'industry_id' => $industry->id
                        ]);
                    });
                }
            }
        });

        $info = CompanyDetail::updateOrCreate(['company_id' => $this->company->id], $details);
        $this->info('Detail for company: ' . $this->company->name);
        return $info;
    }
}
